package org.hugoduncan.appam.config.impl;

import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.abdera.model.Collection;
import org.apache.abdera.model.Element;
import org.apache.abdera.model.ExtensibleElement;
import org.apache.abdera.model.Service;
import org.apache.abdera.model.Workspace;
import org.apache.abdera.xpath.XPath;
import org.apache.abdera.xpath.XPathException;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hugoduncan.appam.component.BasicService;
import org.hugoduncan.appam.component.BasicWorkspace;
import org.hugoduncan.appam.component.FileSystemCollection;
import org.hugoduncan.appam.config.ComponentFactory;
import org.hugoduncan.appam.model.CollectionComponent;
import org.hugoduncan.appam.model.Component;
import org.hugoduncan.appam.model.EntryComponent;
import org.hugoduncan.appam.model.ServiceComponent;
import org.hugoduncan.appam.model.WorkspaceComponent;

/**
 * Build components from specification
 * 
 * @author duncanhugo
 *
 * TODO: remove appam namespace from the service introspection document
 */
public class ComponentFactoryImpl implements ComponentFactory
{
    private static final Log log = LogFactory.getLog(ComponentFactoryImpl.class);
    private static final QName componentQName=
        new QName("http://hugoduncan.org/atom/appam#","component","appam");
    private static final QName entryComponentQName=
        new QName("http://hugoduncan.org/atom/appam#","entrycomponent","appam");
    
    public ServiceComponent construct(Service service) {
        
        debugWrite(service);

        ServiceComponent component=instantiate(
                service, ServiceComponent.class, BasicService.class);
        
        if (component==null)
            return null;

        for (Workspace workspace:service.getWorkspaces())
        {
            WorkspaceComponent wsc=instantiate(
                    workspace, WorkspaceComponent.class, BasicWorkspace.class);
            if (wsc!=null)
            {
                component.add(wsc);
                for (Collection collection:workspace.getCollections())
                {
                    CollectionComponent cc=instantiate(
                            collection, CollectionComponent.class, FileSystemCollection.class);
                    if (cc!=null)
                    {
                        // see if the component wants it configuration
                        assignConfiuration(cc, collection);
                        wsc.add(cc);
                    }
                    for (Element element:collection.getExtensions(entryComponentQName))
                    {
                        //if (element.getQName().getLocalPart().equals("entrycomponent"))
                        {
                            EntryComponent ec=instantiate(element, EntryComponent.class);
                            cc.add(ec);
                        }
                    }
                }
            }
        }

        discardExtensions(service);
        
        debugWrite(service);
        
        // see if the component wants it configuration
        assignConfiuration(component, service);
        return component;
    }


    @SuppressWarnings("unchecked")
    private void discardExtensions(Service service) {
        Map<String, String> ns=XPath.INSTANCE.getDefaultNamespaces();
        if (!ns.containsKey("appam"))
        {
            ns.put("appam", "http://hugoduncan.org/atom/appam#");
            XPath.INSTANCE.setDefaultNamespaces(ns);
        }
        try {
            List<Element> nodes=XPath.INSTANCE.selectNodes("//appam:entrycomponent", service);
            for (Element element:nodes)
            {
                element.discard();
            }
            nodes=XPath.INSTANCE.selectNodes("//appam:component", service);
            for (Element element:nodes)
            {
                element.discard();
            }
//            nodes=XPath.INSTANCE.selectNodes("//*[comment()]", service);
//            for (Element element:nodes)
//            {
 //               element.discard();
//            }
        } catch (XPathException e) {
            log.error("Invalid XPATH",e);
        }   
    }

    private void debugWrite(Service service)
    {
        if (log.isDebugEnabled())
        {
            StringWriter sw=new StringWriter();
            try {
                service.writeTo(sw);
            } catch (IOException e) {
                log.error("Couldn't output service");
            }
            log.debug(sw.toString());
        }
    }


    @SuppressWarnings("unchecked")
    public <T extends Component> T instantiate(
            ExtensibleElement element, 
            Class<T> clazz,
            Class<? extends T> defaultClass)
    {
        T component=null;
        Element componentElement = element.getExtension(componentQName);
        try {
            if (componentElement==null)
                component=(T) defaultClass.newInstance();
            else
            {
                component = instantiate(componentElement, clazz);
            }
            
            return component;
        } catch (InstantiationException e) {
            log.error(e);
        } catch (IllegalAccessException e) {
            log.error(e);
        }
        return null;
    }


    private <T extends Component> T instantiate(Element componentElement, Class<T> clazz) 
    {
        T component=construct(componentElement.getAttributeValue("class"),clazz);
        Element property=componentElement.getFirstChild();
        while (property!=null)
        {
            setValue(component, property.getQName().getLocalPart(), 
                     property.getText());
            property=property.getNextSibling();
        }
        return component;
    }
    

    private <T,V> void setValue(T component, String propertyName, V text) 
    {
        try {
            BeanUtils.setProperty(component, propertyName, text);
        } catch (IllegalAccessException e) {
            log.error(e);
        } catch (InvocationTargetException e) {
            log.error(e);
        }
    }

    @SuppressWarnings("unchecked")
    public <T extends Component> T construct(String name, Class<T> clazz)
    {
        try {
            Class<?> componentClass=Class.forName(name);
            T component=(T) componentClass.newInstance();
            return component;
        } catch (ClassNotFoundException e) {
            log.error(e);
        } catch (InstantiationException e) {
            log.error(e);
        } catch (IllegalAccessException e) {
            log.error(e);
        }
        return null;
    }
    
    private void assignConfiuration(ServiceComponent component, Service service) {
        setValue(component,"service",service);
    }
    
    private void assignConfiuration(
            CollectionComponent component, Collection collection) {
        setValue(component,"collection",collection);
    }


    public WorkspaceComponent construct(Workspace workspace) {
        // TODO Auto-generated method stub
        return null;
    }

    public CollectionComponent construct(Collection collection) {
        // TODO Auto-generated method stub
        return null;
    }

}
